﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SCS.Common.Models;
using SCS.DAL;
using System.Security.Cryptography;
using SCS.Common;
namespace SCS.Business
{
    public class MembershipService : IMembershipService
    {
        private const int SaltSize = 24;
        private LoginAttemptStatus _lastLoginStatus = LoginAttemptStatus.LoginSuccessful;
        IUnitOfWork _unitOfWork;
        public MembershipService()
        {
            _unitOfWork = new UnitOfWork();
        }

        public MembershipService(SCSContext context)
        {
            _unitOfWork = new UnitOfWork(context);
        }

        public IList<MembershipUser> SearchAdminUser(string searchString)
        {
            searchString = StringUtils.SafePlainText(searchString);
            return _unitOfWork.MembershipUser.SearchAdminUser(searchString);
        }

        #region Teacher User
        public IList<MembershipUser> GetAllTeacherUser()
        {
            return _unitOfWork.MembershipUser.GetAllTeacherUser();
        }
        public MembershipUser GetTeacherAccountByID(int id)
        {
            return _unitOfWork.MembershipUser.Get(m => m.userID == id).FirstOrDefault();
        }

        public MembershipUser SetRoleForTeacher(MembershipUser membershipUser, int roleID)
        {
            membershipUser.MembershipRoles.Clear();
            membershipUser.MembershipRoles = _unitOfWork.MembershipRole.Get(m => m.roleID == roleID).ToList();
            return membershipUser;
        }

        public bool UpdateTeacherAccount(MembershipUser membershipUser)
        {
            try
            {

                _unitOfWork.MembershipUser.Update(membershipUser);
                _unitOfWork.Commit();
                return true;
            }
            catch (Exception e)
            {
                return false;
            }
        }

        public IList<MembershipUser> SearchTeacherUser(string searchString)
        {
            searchString = StringUtils.SafePlainText(searchString);
            return _unitOfWork.MembershipUser.SearchTeacherUser(searchString);
        }
        #endregion

        #region ParentsUser
        public IList<MembershipUser> GetAllParentsUser()
        {
            return _unitOfWork.MembershipUser.GetAllParentsUser();
        }
        public IList<MembershipUser> SearchParentsUser(string searchString)
        {
            searchString = StringUtils.SafePlainText(searchString);
            return _unitOfWork.MembershipUser.SearchParentsUser(searchString);
        }

        public MembershipUser GetParentAccountByID(int id)
        {
            return _unitOfWork.MembershipUser.Get(m => m.userID == id).FirstOrDefault();
        }

        public bool UpdateParentAccount(MembershipUser membershipUser)
        {
            try
            {
                _unitOfWork.MembershipUser.Update(membershipUser);
                _unitOfWork.Commit();
                return true;
            }
            catch (Exception e)
            {
                return false;
            }
        }

        #endregion
        public int GetMemberID()
        {
            return (_unitOfWork.MembershipUser.GetAll().Max(t => (int?)t.userID) ?? 0) + 1;
        }
        public MembershipUser SanitizeUser(MembershipUser membershipUser)
        {
            membershipUser.email = StringUtils.SafePlainText(membershipUser.email);
            membershipUser.password = StringUtils.SafePlainText(membershipUser.password);
            membershipUser.userName = StringUtils.SafePlainText(membershipUser.userName);
            return membershipUser;
        }
        /// <summary>
        /// Create a salt for the password hash (just makes it a bit more complex)
        /// </summary>
        /// <param name="size"></param>
        /// <returns></returns>
        private static string CreateSalt(int size)
        {
            // Generate a cryptographic random number.
            var rng = new RNGCryptoServiceProvider();
            var buff = new byte[size];
            rng.GetBytes(buff);

            // Return a Base64 string representation of the random number.
            return Convert.ToBase64String(buff);
        }

        #region HashPassword
        /// <summary>
        /// Generate a hash for a password, adding a salt value
        /// </summary>
        /// <param name="plainText"></param>
        /// <param name="salt"></param>
        /// <returns></returns>
        private static string GenerateSaltedHash(string plainText, string salt)
        {
            // http://stackoverflow.com/questions/2138429/hash-and-salt-passwords-in-c-sharp

            var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
            var saltBytes = Encoding.UTF8.GetBytes(salt);

            // Combine the two lists
            var plainTextWithSaltBytes = new List<byte>(plainTextBytes.Length + saltBytes.Length);
            plainTextWithSaltBytes.AddRange(plainTextBytes);
            plainTextWithSaltBytes.AddRange(saltBytes);

            // Produce 256-bit hashed value i.e. 32 bytes
            HashAlgorithm algorithm = new SHA256Managed();
            var byteHash = algorithm.ComputeHash(plainTextWithSaltBytes.ToArray());
            return Convert.ToBase64String(byteHash);
        }
        #endregion

        #region Status Codes
        public string ErrorCodeToString(MembershipCreateStatus createStatus)
        {
            // See http://go.microsoft.com/fwlink/?LinkID=177550 for
            // a full list of status codes.
            switch (createStatus)
            {
                case MembershipCreateStatus.DuplicateUserName:
                    return "Username already exists. Please enter a different user name.";

                case MembershipCreateStatus.DuplicateEmail:
                    return "A username for that e-mail address already exists. Please enter a different e-mail address.";

                case MembershipCreateStatus.InvalidPassword:
                    return "The password provided is invalid. Please enter a valid password value.";

                case MembershipCreateStatus.InvalidEmail:
                    return "The e-mail address provided is invalid. Please check the value and try again.";

                case MembershipCreateStatus.InvalidAnswer:
                    return "The password retrieval answer provided is invalid. Please check the value and try again.";

                case MembershipCreateStatus.InvalidQuestion:
                    return "The password retrieval question provided is invalid. Please check the value and try again.";

                case MembershipCreateStatus.InvalidUserName:
                    return "The user name provided is invalid. Please check the value and try again.";

                case MembershipCreateStatus.ProviderError:
                    return "The authentication provider returned an error. Please verify your entry and try again. If the problem persists, please contact your system administrator.";

                case MembershipCreateStatus.UserRejected:
                    return "The user creation request has been canceled. Please verify your entry and try again. If the problem persists, please contact your system administrator.";

                default:
                    return "An unknown error occurred. Please verify your entry and try again. If the problem persists, please contact your system administrator.";
            }

        }
        #endregion

        /// <summary>
        /// Return last login status
        /// </summary>
        public LoginAttemptStatus LastLoginStatus
        {
            get { return _lastLoginStatus; }
        }

        /// <summary>
        /// Validate a user by password
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="password"></param>
        /// <param name="maxInvalidPasswordAttempts"> </param>
        /// <returns></returns>
        public bool ValidateUser(string userName, string password, int maxInvalidPasswordAttempts)
        {
            userName = StringUtils.SafePlainText(userName);
            password = StringUtils.SafePlainText(password);

            _lastLoginStatus = LoginAttemptStatus.LoginSuccessful;

            var user = _unitOfWork.MembershipUser.GetUser(userName);

            if (user == null)
            {
                _lastLoginStatus = LoginAttemptStatus.UserNotFound;
                return false;
            }

            if (user.isLockedOut)
            {
                _lastLoginStatus = LoginAttemptStatus.UserLockedOut;
                return false;
            }

            var salt = user.passwordSalt;
            var hash = GenerateSaltedHash(password, salt);
            var passwordMatches = hash == user.password;

            if (!passwordMatches)
            {
                _lastLoginStatus = LoginAttemptStatus.PasswordIncorrect;
                return false;
            }

            return _lastLoginStatus == LoginAttemptStatus.LoginSuccessful;
        }

        #region Create User
        /// <summary>
        /// Creates a new, unsaved user, with default (empty) values
        /// </summary>
        /// <returns></returns>
        public MembershipUser CreateEmptyUser()
        {
            var now = DateTime.UtcNow;

            return new MembershipUser
            {
                userName = string.Empty,
                password = string.Empty,
                email = string.Empty,
                createDate = now,
                lastPasswordChangedDate = now,
                isLockedOut = false
            };
        }

        /// <summary>
        /// Create new user
        /// </summary>
        /// <param name="newUser"></param>
        /// <returns></returns>
        public MembershipCreateStatus CreateUser(MembershipUser newUser, int roleID)
        {
            newUser = SanitizeUser(newUser);

            var status = MembershipCreateStatus.Success;

            if (string.IsNullOrEmpty(newUser.userName))
            {
                status = MembershipCreateStatus.InvalidUserName;
            }

            // get by username
            if (_unitOfWork.MembershipUser.GetUser(newUser.userName) != null)
            {
                status = MembershipCreateStatus.DuplicateUserName;
            }

            // Add get by email address
            if (_unitOfWork.MembershipUser.GetUserByEmail(newUser.email) != null)
            {
                status = MembershipCreateStatus.DuplicateEmail;
            }

            if (string.IsNullOrEmpty(newUser.password))
            {
                status = MembershipCreateStatus.InvalidPassword;
            }

            if (status == MembershipCreateStatus.Success)
            {
                // Hash the password
                var salt = CreateSalt(SaltSize);
                var hash = GenerateSaltedHash(newUser.password, salt);
                newUser.password = hash;
                newUser.passwordSalt = salt;

                // Set dates
                newUser.createDate = DateTime.UtcNow;
                newUser.lastPasswordChangedDate = DateTime.Now;
                newUser.MembershipRoles = _unitOfWork.MembershipRole.Get(m => m.roleID == roleID).ToList();
                newUser.isLockedOut = false;
                try
                {
                    _unitOfWork.MembershipUser.Add(newUser);

                    _unitOfWork.Commit();
                }
                catch (Exception)
                {
                    status = MembershipCreateStatus.UserRejected;
                }
            }

            return status;
        }
        #endregion


        
        #region Get User
        /// <summary>
        /// Get a user by username
        /// </summary>
        /// <param name="username"></param>
        /// <returns></returns>
        public MembershipUser GetUser(string username)
        {
            return _unitOfWork.MembershipUser.GetUser(username);
        }

        /// <summary>
        /// GetAllUser
        /// </summary>
        /// <returns>AllUser</returns>
        public IEnumerable<MembershipUser> GetAllUser()
        {
            return _unitOfWork.MembershipUser.GetAll().Where(m => m.MembershipRoles.FirstOrDefault().roleID != 1);
        }
        public List<string> AutoComplete(string searchString)
        {
           return _unitOfWork.MembershipUser.Get(m => m.MembershipRoles.FirstOrDefault().roleID != 1 && m.userName.Contains(searchString)).Select(t => t.userName).ToList<string>();
        }
        /// <summary>
        /// Get a user by email address
        /// </summary>
        /// <param name="email"></param>
        /// <returns></returns>
        public MembershipUser GetUserByEmail(string email)
        {
            email = StringUtils.SafePlainText(email);
            return _unitOfWork.MembershipUser.GetUserByEmail(email);
        }

        /// <summary>
        /// Get users from a list of Id's
        /// </summary>
        /// <param name="guids"></param>
        /// <returns></returns>
        public IList<MembershipUser> GetUsersById(int id)
        {
            return _unitOfWork.MembershipUser.GetUsersById(id);
        }
        #endregion

        /// <summary>
        /// Return the roles found for this username
        /// </summary>
        /// <param name="username"></param>
        /// <returns></returns>
        public string[] GetRolesForUser(string username)
        {
            username = StringUtils.SafePlainText(username);
            var roles = new List<string>();
            var user = _unitOfWork.MembershipUser.GetUser(username);

            if (user != null)
            {
                roles.AddRange(user.MembershipRoles.Select(role => role.roleName));
            }

            return roles.ToArray();
        }

        /// <summary>
        /// Change the user's password
        /// </summary>
        /// <param name="user"> </param>
        /// <param name="oldPassword"></param>
        /// <param name="newPassword"></param>
        /// <returns></returns>
        public bool ChangePassword(MembershipUser user, string oldPassword, string newPassword)
        {
            try
            {
                oldPassword = StringUtils.SafePlainText(oldPassword);
                newPassword = StringUtils.SafePlainText(newPassword);

                //n3oCacheHelper.Clear(user.UserName);
                var existingUser = _unitOfWork.MembershipUser.GetCurrentUser(user.userID);
                var salt = existingUser.passwordSalt;
                var oldHash = GenerateSaltedHash(oldPassword, salt);

                if (oldHash != existingUser.password)
                {
                    // Old password is wrong - do not allow update
                    return false;
                }

                // Cleared to go ahead with new password
                salt = CreateSalt(SaltSize);
                var newHash = GenerateSaltedHash(newPassword, salt);

                existingUser.password = newHash;
                existingUser.passwordSalt = salt;
                _unitOfWork.MembershipUser.Update(existingUser);
                _unitOfWork.Commit();
            }
            catch (Exception ex)
            {
                return false;
            }
            return true;
        }

        /// <summary>
        /// Reset a users password
        /// </summary>
        /// <param name="user"></param>
        /// <param name="newPassword"> </param>
        /// <returns></returns>
        public bool ResetPassword(string userName, string phone, out string error, out string newPassword)
        {
            MembershipUser existingUser = GetUser(userName);
            if (existingUser == null)
            {
                error = "Tên đăng nhập ko tồn tại";
                newPassword = string.Empty;
                return false;
            }
            else
            {
                if (existingUser.lastPasswordChangedDate.AddHours(12) > DateTime.Now)
                {
                    int estimate = DateTime.Now.Hour - existingUser.lastPasswordChangedDate.Hour;
                    error = "Vui lòng chờ " + estimate.ToString() + " để lấy lại mật khẩu sau";
                    newPassword = string.Empty;
                    return false;
                }
                if (existingUser.Parents.Count > 0)
                {
                    if (existingUser.Parents.FirstOrDefault().mobile != phone)
                    {
                        error = "Số điện thoại không đúng";
                        newPassword = string.Empty;
                        return false;
                    }
                    else
                    {
                        newPassword = StringUtils.RandomString(8);
                        var salt = CreateSalt(SaltSize);
                        var newHash = GenerateSaltedHash(newPassword, salt);
                        existingUser.password = newHash;
                        existingUser.passwordSalt = salt;
                        _unitOfWork.MembershipUser.Update(existingUser);
                        _unitOfWork.Commit();
                        error = "Mật khẩu mới đã gửi đến số điện thoại của bạn";
                        return true;
                    }
                }
                else if (existingUser.Teachers.Count > 0)
                {
                    if (existingUser.Teachers.FirstOrDefault().mobile != phone)
                    {
                        error = "Số điện thoại không đúng";
                        newPassword = string.Empty;
                        return false;
                    }
                    else
                    {
                        newPassword = StringUtils.RandomString(8);
                        var salt = CreateSalt(SaltSize);
                        var newHash = GenerateSaltedHash(newPassword, salt);
                        existingUser.password = newHash;
                        existingUser.passwordSalt = salt;
                        _unitOfWork.MembershipUser.Update(existingUser);
                        _unitOfWork.Commit();
                        error = "Mật khẩu mới đã gửi đến số điện thoại của bạn";
                        return true;
                    }
                }
            }
            error = "Không thể lấy lại mật khẩu";
            newPassword = string.Empty;
            return false;
            
        }

        /// <summary>
        /// Get all members
        /// </summary>
        /// <returns></returns>
        public IList<MembershipUser> GetAll()
        {
            return _unitOfWork.MembershipUser.GetAll().ToList();
        }

        /// <summary>
        /// SearchMembers
        /// </summary>
        /// <param name="username">username</param>
        /// <param name="amount">amount</param>
        /// <returns>user list</returns>
        public IList<MembershipUser> SearchMembers(string username, int amount)
        {
            return _unitOfWork.MembershipUser.SearchMembers(StringUtils.SafePlainText(username), amount);
        }


        /// <summary>
        /// Get user by id
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public MembershipUser GetUser(int id)
        {
            return _unitOfWork.MembershipUser.GetCurrentUser(id);
        }

        /// <summary>
        /// Delete a member
        /// </summary>
        /// <param name="user"></param>
        public void Delete(MembershipUser user)
        {/*
            // Delete all private messages from this user
            var msgsToDelete = new List<PrivateMessage>();
            msgsToDelete.AddRange(user.PrivateMessagesSent);
            foreach (var msgToDelete in msgsToDelete)
            {
                _privateMessageService.DeleteMessage(msgToDelete);
            }

            msgsToDelete.Clear();
            msgsToDelete.AddRange(user.PrivateMessagesReceived);
            foreach (var msgToDelete in msgsToDelete)
            {
                _privateMessageService.DeleteMessage(msgToDelete);
            }

            // Delete all badge times last checked
            var badgeTypesTimeLastCheckedToDelete = new List<BadgeTypeTimeLastChecked>();
            badgeTypesTimeLastCheckedToDelete.AddRange(user.BadgeTypesTimeLastChecked);
            foreach (var badgeTypeTimeLastCheckedToDelete in badgeTypesTimeLastCheckedToDelete)
            {
                _badgeService.DeleteTimeLastChecked(badgeTypeTimeLastCheckedToDelete);
            }

            // Delete all points from this user
            var pointsToDelete = new List<MembershipUserPoints>();
            pointsToDelete.AddRange(user.Points);
            foreach (var pointToDelete in pointsToDelete)
            {
                _membershipUserPointsService.Delete(pointToDelete);
            }

            // Delete all topic notifications
            var topicNotificationsToDelete = new List<TopicNotification>();
            topicNotificationsToDelete.AddRange(user.TopicNotifications);
            foreach (var topicNotificationToDelete in topicNotificationsToDelete)
            {
                _topicNotificationService.Delete(topicNotificationToDelete);
            }

            // Delete all user's votes
            var votesToDelete = new List<Vote>();
            votesToDelete.AddRange(user.Votes);
            foreach (var voteToDelete in votesToDelete)
            {
                _voteService.Delete(voteToDelete);
            }

            // Delete all user's badges
            var badgesToDelete = new List<Badge>();
            badgesToDelete.AddRange(user.Badges);
            foreach (var badgeToDelete in badgesToDelete)
            {
                _badgeService.Delete(badgeToDelete);
            }

            // Delete all user's category notifications
            var categoryNotificationsToDelete = new List<CategoryNotification>();
            categoryNotificationsToDelete.AddRange(user.CategoryNotifications);
            foreach (var categoryNotificationToDelete in categoryNotificationsToDelete)
            {
                _categoryNotificationService.Delete(categoryNotificationToDelete);
            }

            // Just clear the roles, don't delete them
            user.Roles.Clear();

            _membershipRepository.Delete(user);
          */
        }

        /// <summary>
        /// MemberCount
        /// </summary>
        /// <returns>user quantity</returns>
        public int MemberCount()
        {
            return _unitOfWork.MembershipUser.MemberCount();
        }

        /// <summary>
        /// Save user (does NOT update password data)
        /// </summary>
        /// <param name="user"></param>
        public void Save(MembershipUser user)
        {

            user = SanitizeUser(user);

            _unitOfWork.MembershipUser.Update(user);
            _unitOfWork.Commit();
        }

        /// <summary>
        /// Save user (does NOT update password data)
        /// </summary>
        /// <param name="user"></param>
        public void ProfileUpdated(MembershipUser user)
        {
            /*   var e = new UpdateProfileEventArgs { User = user, Api = _api };
               EventManager.Instance.FireBeforeProfileUpdated(this, e);

               if (!e.Cancel)
               {
                   EventManager.Instance.FireAfterProfileUpdated(this, new UpdateProfileEventArgs { User = user, Api = _api });
                   _activityService.ProfileUpdated(user);
               }
             */
        }

        /// <summary>
        /// Unlock a user
        /// </summary>
        /// <param name="username"></param>
        /// <param name="resetPasswordAttempts">If true, also reset password attempts to zero</param>
        public void UnlockUser(string username, bool resetPasswordAttempts)
        {
            {
                var user = GetUser(username);

                if (user == null)
                {
                    throw new ApplicationException(("Members.CantUnlock"));
                }

                var existingUser = _unitOfWork.MembershipUser.GetCurrentUser(user.userID);

                user.isLockedOut = false;
                user.MembershipRoles = existingUser.MembershipRoles;
                user.password = existingUser.password;
                user.passwordSalt = existingUser.passwordSalt;
                _unitOfWork.MembershipUser.Update(user);
                _unitOfWork.Commit();
            }
        }


    }
}
